/* 
 * Copyright [2018] [Alex/libo(liboware@gmail.com)]
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hyts.codex.redis.service.impl;

import java.util.Collections;
import java.util.Objects;
import java.util.UUID;

import com.hyts.codex.redis.model.JedisConnection;
import com.hyts.codex.redis.service.ILockApi;

import lombok.AllArgsConstructor;
import lombok.Setter;
import redis.clients.jedis.Jedis;

/** 
 * @author LiBo/Alex
 * @version V1.0 
 */
@AllArgsConstructor
public class RLockService implements ILockApi{
    
    /**
     * 默认 0 
     */
    //@Setter
    private final int dbIndex;
        
    /**
     * redisConnection object
     */
    //@Setter
    private final JedisConnection connection;
    
    
    @Setter
    // try lock time out value
    private int tryLockTimeout;
    
    
    /**
     * @constructor：IDGenService
     */
    public RLockService(JedisConnection connection) {
        this.connection = connection;
        dbIndex = 0;
        tryLockTimeout = DEFAULT_LOCK_TIME_OUT;
    }
    
    
   /**
    * @constructor：IDGenService
    */
   public RLockService(JedisConnection connection,int timeout) {
       this.connection = connection;
       dbIndex = 0;
       tryLockTimeout = timeout;
   }
    
    /* 
     * (非 Javadoc)
     * @param key
     * @return
     * @see com.hyts.ssm.api.ILockApi#lock(java.lang.String)  
     * @exception
     */ 
    @Override
    public String lock(String key) {
        return commonTemplate(key,0);
    }

    /* (非 Javadoc)
     * @param key
     * @return
     * @see com.hyts.ssm.api.ILockApi#tryLock(java.lang.String)  
     * @exception
     */ 
    @Override
    public String tryLock(String key) {
        return commonTemplate(key,DEFAULT_LOCK_TIME_OUT);
    }

    /* (非 Javadoc)
     * @param key
     * @param timeout
     * @return
     * @see com.hyts.ssm.api.ILockApi#tryLock(java.lang.String, int)  
     * @exception
     */ 
    @Override
    public String tryLock(String key, int timeout) {
        return commonTemplate(key,timeout);
    }

    /* (非 Javadoc)
     * @param key
     * @param value
     * @return
     * @see com.hyts.ssm.api.ILockApi#unLock(java.lang.String, java.lang.String)  
     * @exception
     */ 
    @Override
    public boolean unLock(String key, String value) {
        Jedis session = connection.openSession().orElse(connection.openSession().get());
        session.select(dbIndex);
        key = "".concat(DEFAULT_LOCK_KEY_PREFIX).concat(key);
        String commandScript = "if redis.call('get',KEYS[1]) = ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        try {
            if (RELEASE_SUCCESS.equals(
                    session.eval(commandScript, Collections.singletonList(key), Collections.singletonList(value)))) {
                return true;
            }else {
                return false;
            }
        } finally {
            if(!Objects.isNull(session)) {
                session.close();
            }
        }
    }
    
    /**
     * common the template code to lock data
     * @return
     * @exception
     */
    private String commonTemplate(String key, int tryTimeout) {
        Jedis session = connection.openSession().orElse(connection.openSession().get());
        session.select(dbIndex);
        key = "".concat(DEFAULT_LOCK_KEY_PREFIX).concat(key);
        String value = genUUID();
        try {
            long start = System.currentTimeMillis();
            long end = tryTimeout*1000+start;
            do {
                if (REDIS_RESULT_SUCCESS
                        .equals(session.set(key, value, DEFAULT_LOCK_NX, DEFAULT_LOCK_EX, DEFAULT_LOCK_EXPIRE_TIME))) {
                    return value;
                } else {
                    return null;
                } 
            }while(end > start);
        } finally {
            if(!Objects.isNull(session)) {
                session.close();
            }
        }
    }
    
    /**
     * wait subclass override
     * @return
     * @exception
     */
    protected String genUUID() {
        return UUID.randomUUID().toString();
    }
    
}
